home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1995 February: Tool Chest / Dev.CD Feb 95 / Dev.CD Feb 95.toast / Sample Code / Pascal Sample 3.0B10 / Source / SampleUtilities.inc1.p < prev    next >
Encoding:
Text File  |  1993-10-13  |  22.6 KB  |  659 lines  |  [TEXT/MPS ]

  1. (******************************************************************************
  2. *
  3. *    Apple Macintosh Developer Technical Support
  4. *
  5. *    Code for the utility routines
  6. *
  7. *    Program:    Sample 3.0
  8. *    FILE:        SampleUtilties.inc1.p - Pascal implementation
  9. *
  10. *    by:            Matt Deatherage
  11. *
  12. *    Copyright © 1988-1993 Apple Computer, Inc.
  13. *    All rights reserved.
  14. *
  15. *******************************************************************************
  16.  
  17. (*******************************************************************************
  18. * Routines in other files referenced by this code
  19. *******************************************************************************)
  20.  
  21. PROCEDURE DoUpdate(window: WindowPtr);
  22.     EXTERNAL;
  23.  
  24. PROCEDURE DoEvent(theEvent: EventRecord);
  25.     EXTERNAL;
  26.  
  27. {$S Main}
  28. (******************************************************************************
  29. *
  30. * Public: IsDAWindow
  31. *
  32. * Checks to see if a window belongs to a desk accessory.  All DAs have negative
  33. * windowKind fields, so that's how we check (presuming it's not NIL).
  34. *
  35. ******************************************************************************)
  36.  
  37. FUNCTION IsDAWindow(window: WindowPtr): BOOLEAN;
  38.  
  39. BEGIN
  40.     IF window = NIL THEN
  41.         IsDAWindow := FALSE
  42.     ELSE                                 { DA windows have negative windowKinds }
  43.         IsDAWindow := (WindowPeek(window)^.windowKind < 0);
  44. END; {IsDAWindow}
  45.  
  46. {$S Main}
  47. (******************************************************************************
  48. *
  49. * Public: IsAppWindow
  50. *
  51. * Checks to see if a window belongs to the application.  If the window pointer
  52. * passed is NIL, it can't be an application window.  WindowKind values that are
  53. * negative belong to the system, and all windowKind values less than userKind
  54. * are reserved for Apple (except dialogKind, which means it's a dialog).
  55. *
  56. * We only return TRUE if the windowKind is equal to userKind.  If you add
  57. * other kinds of windows to this application, you'll have to change how
  58. * this routine works.
  59. *
  60. ******************************************************************************)
  61.  
  62. FUNCTION IsAppWindow(window: WindowPtr): BOOLEAN;
  63.  
  64. BEGIN
  65.     IF window = NIL THEN
  66.         IsAppWindow := FALSE
  67.     ELSE                 { application windows have windowKinds = userKind (8) }
  68.         IsAppWindow := (WindowPeek(window)^.windowKind = userKind);
  69. END; {IsAppWindow}
  70.  
  71. {$S Dialogs}
  72. (******************************************************************************
  73. *
  74. * Public: ClassifyKey
  75. *
  76. * Takes an event record (presumed to contain a keypress event) and classifies
  77. * the key within it on several criteria:  is it a digit, hex digit, letter,
  78. * non-control key, command key, "accept" key (enter/return), "cancel" key
  79. * (escape or Command-period), or an editing key?
  80. *
  81. * The parameter is a pointer to an event record.  This routine uses the
  82. * not-well-known "short circuit" operators present in both MPW Pascal and
  83. * THINK Pascal -- "&" for AND and "|" for OR.  The compiler evaluates
  84. * both sides of the "&" or "|" expression, but as soon as it knows what
  85. * the outcome will be it stops.  For example, "TRUE | FALSE" never evaulates
  86. * FALSE, because it already knows the expression is TRUE because the first
  87. * part was TRUE.  Similarly, "FALSE & TRUE" never evaulates TRUE.  Makes it
  88. * look a bit like C.
  89. *
  90. ******************************************************************************)
  91.  
  92. FUNCTION ClassifyKey(theEventPtr: EventRecordPtr): INTEGER;
  93.  
  94. VAR
  95.     theFlags,                        { the flags we'll return }
  96.     theChar: INTEGER;                { the character in question }
  97.  
  98. BEGIN
  99.     theFlags := 0;                    { assume no characteristics }
  100.     theChar := BAND(theEventPtr^.message, charCodeMask);
  101.                                     { get the character from the record }
  102.     
  103.     { Is this an editing key? }
  104.     
  105.     IF (theChar = kDeleteKey) | (theChar = kLeftArrowKey) | (theChar =
  106.        kRightArrowKey) | (theChar = kUpArrowKey) | (theChar = kDownArrowKey) |
  107.        (theChar = kEscapeKey) | (theChar = kTabKey) THEN
  108.         theFlags := BOR(theFlags, kEditKey);
  109.  
  110.     { Is it a digit?  If so, it's a hex digit also.  It's also not a letter, 
  111.     { cancel key or accept key, so don't bother checking }
  112.     
  113.     IF ((theChar >= ORD('0')) & (theChar <= ORD('9'))) THEN
  114.         theFlags := BOR(theFlags, kDigitKey + kHexDigitKey)
  115.     ELSE
  116.         BEGIN
  117.             IF ((theChar >= ORD('A')) & (theChar <= ORD('Z'))) | 
  118.                ((theChar >= ORD('a')) & (theChar <= ORD('z'))) THEN
  119.                 theFlags := BOR(theFlags, kLetterKey);
  120.  
  121.             IF ((theChar >= ORD('A')) & (theChar <= ORD('F'))) | 
  122.                ((theChar >= ORD('a')) & (theChar <= ORD('f'))) THEN
  123.                 theFlags := BOR(theFlags, kHexDigitKey);
  124.  
  125.             IF ((theChar = ORD('.')) & (BAND(theEventPtr^.modifiers,
  126.                cmdKey) <> 0)) | (theChar = kEscapeKey) THEN
  127.                 theFlags := BOR(theFlags, kCancelKey);
  128.  
  129.             IF ((theChar = kReturnKey) | (theChar = kEnterKey)) THEN
  130.                 theFlags := BOR(theFlags, kAcceptKey);
  131.         END;
  132.  
  133.     { Is this a control key? }
  134.     
  135.     IF (theChar > 31) THEN
  136.         theFlags := BOR(theFlags, kNonControlKey);
  137.  
  138.     { Is the command key modifier down? }
  139.  
  140.     IF (BAND(theEventPtr^.modifiers, cmdKey) <> 0) THEN
  141.         theFlags := BOR(theFlags, kCommandKey);
  142.  
  143.     ClassifyKey := theFlags;
  144.  
  145. END; { ClassifyKey }
  146.  
  147. {$S Main}
  148. (******************************************************************************
  149. *
  150. * private: SampleAEIdleProc
  151. *
  152. * We need to pass an idle routine to AEInteractWithUser so that we can properly
  153. * Handle activate, update and suspend/resume events (as well as other Apple
  154. * events, maybe) while we wait.  This routine calls our main event handling
  155. * routine for update, activate and OS events, and puts us to sleep for the
  156. * maximum possible time when we get a NULL event, with no cursor handling.
  157. *
  158. ******************************************************************************)
  159.  
  160. FUNCTION SampleAEIdleProc(VAR theEvent: EventRecord; VAR sleepTime: LONGINT;
  161.                           VAR mouseRgn: RgnHandle): BOOLEAN;
  162.  
  163. BEGIN
  164.     CASE theEvent.what OF
  165.         updateEvt, activateEvt, osEvt:
  166.             DoEvent(theEvent);
  167.         nullEvent:
  168.             BEGIN
  169.                 mouseRgn := NIL;          { no Cursor handling }
  170.                 sleepTime := MAXLONGINT; { we don't need lots of time, either }
  171.             END;
  172.         OTHERWISE;
  173.     END;
  174.     SampleAEIdleProc := FALSE;             { we're always willing to wait longer }
  175. END;
  176.  
  177. {$S Main}
  178. (******************************************************************************
  179. *
  180. * Public: OKToInteract
  181. *
  182. * We always call OKToInteract before we talk to the user, just to make sure
  183. * it's fine to do so.  Even when the code we're executing can't directly be
  184. * called via an Apple Event right now, it's still a good habit to get into
  185. * so you don't find yourself talking to the user at inappropriate times
  186. * in the future.  We also set up other user interaction parameters here,
  187. * such as the arrow cursor.
  188. *
  189. * OKToInteract checks to see if there's currently an Apple Event being
  190. * processed and, if there is, and there's a timeout value other than
  191. * kAEDefaultTimeout and kNoTimeout, we set the timeout for _our_ call to
  192. * AEInteractWithUser to 80% of their timeout.  While not foolproof, this mostly
  193. * avoids the problem where someone asks us to, for example, print a file
  194. * with a timeout of two minutes, but we wait forever to see if we can be
  195. * brought to the front to do the printing job dialog.  The original event
  196. * would time out, but we'd still be blinking the process menu and wouldn't
  197. * go on until our application was the current one.  That's not good.
  198. *
  199. * We pass SampleAEIdleProc as the filter procedure, since that's why
  200. * it's there.
  201. *
  202. ******************************************************************************)
  203.  
  204. FUNCTION OKToInteract: BOOLEAN;
  205.  
  206. VAR
  207.     myErr: OSErr;                        { error returned from various calls }
  208.     canWeTalk: BOOLEAN;                    { signal that interaction is OK }
  209.     theAEvent: AppleEvent;                { the current Apple event }
  210.     theRealType: DescType;                { the type as returned by the AE Mgr }
  211.     theTimeout: LONGINT;                { the timeout value }
  212.     theRealSize: Size;                    { the real size of the parameter }
  213.  
  214. BEGIN
  215.     canWeTalk := TRUE;                    { assume we can talk if no AE Mgr }
  216.     IF gHasAppleEvents THEN
  217.         BEGIN
  218.             
  219.             { Get the current event, and get the keyTimeoutAttr from it.
  220.               If there's no event, or no timeout, we'll use a default
  221.               timeout.  We get the timeout as a LONGINT, and it should
  222.               never be longer than that so the cast always succeeds.  but
  223.               we still have to pass variables to get the real descriptor
  224.               type and the real size of the parameter -- we just ignore them. }
  225.             
  226.             myErr := AEGetTheCurrentEvent(theAEvent);
  227.             myErr := AEGetAttributePtr(theAEvent, keyTimeoutAttr,
  228.                                        typeLongInteger, theRealType,
  229.                                        @theTimeout, 4, theRealSize);
  230.             IF (myErr <> noErr) THEN
  231.                 theTimeout := kAEDefaultTimeout
  232.             ELSE IF (theTimeout <> kAEDefaultTimeout) AND 
  233.                     (theTimeout <> kNoTimeout) THEN
  234.                 theTimeout := (LONGINT(theTimeout) DIV 5) * 4;
  235.                                         { about 80% of the caller's value }
  236.  
  237.             myErr := AEInteractWithUser(theTimeout, NIL, @SampleAEIdleProc);
  238.             canWeTalk := (myErr = noErr);
  239.         END;
  240.  
  241.     IF canWeTalk THEN
  242.         SetCursor(arrow);
  243.  
  244.     OKToInteract := canWeTalk;
  245.  
  246. END; { OKToInteract }
  247.  
  248. {$S Main}
  249. (******************************************************************************
  250. *
  251. * Public: MustInteract
  252. *
  253. * This routine is very much like OKToInteract, but the timeout is always
  254. * kNoTimeout.  This routine does not return until we can interact with the
  255. * user.
  256. *
  257. * DoPromptSave requires this -- it's not acceptable to either overwrite a
  258. * document on disk _or_ to lose changes when responding to an Apple Event.
  259. * We must let the user decide what to do in that case.
  260. *
  261. ******************************************************************************)
  262.  
  263. PROCEDURE MustInteract;
  264.  
  265. VAR
  266.     myErr: OSErr;                        { error returned from various calls }
  267.  
  268. BEGIN
  269.  
  270.     IF gHasAppleEvents THEN
  271.         myErr := AEInteractWithUser(kNoTimeout, NIL, @SampleAEIdleProc);
  272.  
  273.     SetCursor(arrow);
  274.  
  275. END; { MustInteract }
  276.  
  277. {$S Main}
  278. (******************************************************************************
  279. *
  280. * Public: AskUser
  281. *
  282. * Asks the user about something, and returns TRUE if they selected the
  283. * default Button (item #1).  The Alert resource ID is the passed parameter.
  284. *
  285. ******************************************************************************)
  286.  
  287. FUNCTION AskUser(alertNum: INTEGER): BOOLEAN;
  288.  
  289. VAR
  290.     itemHit: INTEGER;                    { the item the user selected }
  291.  
  292. BEGIN
  293.     itemHit := 0;
  294.     IF OKToInteract THEN
  295.         BEGIN
  296.             itemHit := Alert(alertNum, NIL);
  297.         END;
  298.     AskUser := (itemHit = 1);
  299. END; {AskUser}
  300.  
  301. {$S Main}
  302. (******************************************************************************
  303. *
  304. * Public: AlertUser
  305. *
  306. * Presents the user with an alert telling him something, then goes away.
  307. * The resource ID of the alert is the passed parameter.  We ignore whatever
  308. * button the user picks, because we don't care.
  309. *
  310. ******************************************************************************)
  311.  
  312. PROCEDURE AlertUser(alertNum: INTEGER);
  313.  
  314. VAR
  315.     itemHit: INTEGER;                    { the item the user selected }
  316.  
  317. BEGIN
  318.     IF OKToInteract THEN
  319.         BEGIN
  320.             itemHit := Alert(alertNum, NIL);
  321.         END;
  322. END; { AlertUser }
  323.  
  324. {$S Main}
  325. (******************************************************************************
  326. *
  327. * Public: UpdateAllAppWindows
  328. *
  329. * This routine walks through all the windows in the window list, and calls
  330. * DoUpdate for each of them if they have anything at all to update.  DoUpdate 
  331. * takes no action if the window isn't an application window.  When this routine
  332. * is done, none of our windows have any pending updates.
  333. *
  334. ******************************************************************************)
  335.  
  336. PROCEDURE UpdateAllAppWindows;
  337.  
  338. VAR
  339.     aWindow: WindowPtr;                        { the window to update }
  340.  
  341. BEGIN
  342.     aWindow := FrontWindow;                    { start with the first window }
  343.     WHILE aWindow <> NIL DO
  344.         BEGIN
  345.             
  346.             { We only update a window if it has a non-empty update Region,
  347.               so we don't do the Region manipulations in BeginUpdate and
  348.               EndUpdate if not necessary }
  349.             
  350.             IF NOT EmptyRgn(WindowPeek(aWindow)^.updateRgn) THEN
  351.                 DoUpdate(aWindow);
  352.             aWindow := GetNextWindow(aWindow);
  353.         END;
  354. END; { UpdateAllAppWindows }
  355.  
  356. {$S Main}
  357. (******************************************************************************
  358. *
  359. * Public: DoPromptSave
  360. *
  361. * This routine presents an alert of the form "Save the <application name>
  362. * document <theName> before <closing/quitting>?   Don't Save/cancel/Save".
  363. * The action is either kClosing or kQuitting, and the return value is
  364. * either kSave, kCancel or kDontSave.  We must be able to interact with
  365. * the user, so we call MustInteract to delay until we can.  That might mean
  366. * Apple Events which call us time out before we get a user's answer, but
  367. * it's better to time out than to accidentally lose data.
  368. *
  369. ******************************************************************************)
  370.  
  371. FUNCTION DoPromptSave(theName: Str63; theAction: INTEGER): INTEGER;
  372.  
  373. VAR
  374.     appNameString: StringHandle;        { the name of our application }
  375.  
  376. BEGIN
  377.     appNameString := GetString(kMissingAppNameStr);        { get our app name }
  378.     ParamText(appNameString^^, theName, '', '');               { substitute it }
  379.     MustInteract;                              { we MUST get the user's choice }
  380.     DoPromptSave := Alert(rActionAlertBase+theAction, NIL);           { ask them }
  381.     ReleaseResource(Handle(appNameString));          { and deallocate the memory }
  382. END; { DoPromptSave }
  383.  
  384. {$S Main}
  385. (******************************************************************************
  386. *
  387. * Public: HandleFileError
  388. *
  389. * This routine maps several common file system errors into alert IDs and
  390. * presents error dialogs, or uses a generic dialog when no specific error
  391. * message is available.  Most alerts also include the name of the document
  392. * we were using, passed as the second parameter.  userCanceledErr is handled
  393. * by ignoring it, so we can call this routine even when we aborted a file
  394. * handling operation because of command-period or "cancel."
  395. ******************************************************************************)
  396.  
  397. PROCEDURE HandleFileError(myErr: OSErr; windTitle: Str255);
  398.  
  399. VAR
  400.     tempErrString: Str255;                { the ASCII value of the error }
  401.     myLongErr: LONGINT;                    { LONGINT copy for formal VAR params }
  402.  
  403. BEGIN
  404.     myLongErr := myErr;
  405.     NumToString(myLongErr, tempErrString);
  406.     ParamText(tempErrString, windTitle, '', '');
  407.     CASE myErr OF
  408.         userCanceledErr: ;             { no action here, but not a weird error }
  409.         ioErr:
  410.             AlertUser(rIOError);
  411.         opWrErr:
  412.             AlertUser(rFileAlreadyOpen);
  413.         permErr:
  414.             AlertUser(rNoPermission);
  415.         wPrErr:
  416.             AlertUser(rDiskWriteProt);
  417.         wrPermErr:
  418.             AlertUser(rCantWriteFile);
  419.         OTHERWISE
  420.             BEGIN
  421.                 NumToString(myErr, tempErrString);
  422.                 ParamText(tempErrString, windTitle, '', '');
  423.                 AlertUser(rSomeWeirdError);
  424.             END;
  425.     END;
  426. END; { HandleFileError }
  427.  
  428. {$S Main}
  429. (******************************************************************************
  430. *
  431. * Public: CheckRequiredAEParms
  432. *
  433. * This routine uses AEGetAttributePtr to search for the missed keyword
  434. * attribute, returning noErr if there isn't one (meaning we processed all
  435. * required AE parameters) and returning errAEEVtNotHandled if there was one,
  436. * because that means we missed a parameter.
  437. *
  438. ******************************************************************************)
  439.  
  440. FUNCTION CheckRequiredAEParms(theAppleEvent: AppleEvent): OSErr;
  441.  
  442. VAR
  443.     myErr: OSErr;                        { error from AE Manager calls }
  444.     attrType: DescType;                    { the real descriptor type (ignored) }
  445.     attrSize: Size;                        { the real attribute size (ignored) }
  446.  
  447. BEGIN
  448.     myErr := AEGetAttributePtr(theAppleEvent, keyMissedKeywordAttr,
  449.                                typeWildCard, attrType, NIL, 0, attrSize);
  450.     IF myErr = errAEDescNotFound THEN
  451.         myErr := noErr
  452.     ELSE IF myErr = noErr THEN
  453.         myErr := errAEEventNotHandled;
  454.     CheckRequiredAEParms := myErr;
  455. END; { CheckRequiredAEParms }
  456.  
  457. {$S Main}
  458. (******************************************************************************
  459. *
  460. * Public: CreateWindowTitle
  461. *
  462. * This routine looks in the rUntitledStrings resource for either "untitled"
  463. * (for the first window) or "untitled " (for the second and later windows) and
  464. * uses them, along with ASCII conversions of window numbers (maintained by the
  465. * gUntitledWindowCount global variable) to Create new "untitled" window titles
  466. * as specified by Macintosh Human Interface Guidelines.
  467. *
  468. ******************************************************************************)
  469.  
  470. PROCEDURE CreateWindowTitle(VAR theString: Str63);
  471.  
  472. VAR
  473.     titleNumberStr,                        { string for the number of the window }
  474.     untitledString: Str255;                { holds "untitled" or "untitled " }
  475.     index: INTEGER;                        { which string do we fetch? }
  476.  
  477. BEGIN
  478.     gUntitledWindowCount := gUntitledWindowCount + 1;
  479.     IF gUntitledWindowCount > 1 THEN    { is this the first window? }
  480.         BEGIN
  481.             index := kUntitledWithSpaceString;
  482.             NumToString(gUntitledWindowCount, titleNumberStr);
  483.         END
  484.     ELSE
  485.         BEGIN
  486.             titleNumberStr := '';
  487.             index := kUntitledNoSpaceString;
  488.         END;
  489.     GetIndString(untitledString, rUntitledStrings, index);
  490.     theString := concat(untitledString, titleNumberStr)
  491. END; { CreateWindowTitle }
  492.  
  493. {$S Main}
  494. (******************************************************************************
  495. *
  496. * Public: DoCopyResource
  497. *
  498. * This function to copy a resource between two files is pretty much taken
  499. * from Inside Macintosh: Macintosh Toolbox Essentials, page 7-29.  That
  500. * routine has more comments, but all the useful ones are here.
  501. *
  502. * This routine has one change -- it saves and restores the current resource
  503. * file.  This is a utility routine, and the code which calls it doesn't
  504. * necessarily expect the resource file to change after calling this routine.
  505. * We also note that this routine does _not_ check to see that the resource
  506. * being added to the destination file doesn't already exist -- if it does,
  507. * you'll wind up with two resources of the same type and ID in the
  508. * destination file.
  509. *
  510. ******************************************************************************)
  511.  
  512. FUNCTION DoCopyResource(theType: ResType; theID, source, dest: INTEGER): OSErr;
  513.  
  514. VAR
  515.     myHandle: Handle;                     { handle to resource to copy}
  516.     myName: Str255;                     { name of resource to copy }
  517.     myType: ResType;                     { ignored, used for GetResInfo }
  518.     myID: INTEGER;                         { ignored, used for GetResInfo }
  519.     oldResFile: INTEGER;                { the resource file on entry }
  520.  
  521. BEGIN
  522.     oldResFile := CurResFile;            { save the original resource file }
  523.     UseResFile(source);                 { set the source resource file}
  524.     myHandle := GetResource(theType, theID); { open the source }
  525.     IF myHandle <> NIL THEN
  526.         BEGIN
  527.             GetResInfo(myHandle, myID, myType, myName); { get resource name}
  528.             DetachResource(myHandle); 
  529.             UseResFile(dest);            { set the destination resource file }
  530.             AddResource(myHandle, theType, theID, myName);
  531.             IF ResError = noErr THEN
  532.                 WriteResource(myHandle);{ write resource data}
  533.         END;
  534.     UseResFile(oldResFile);                { restore original resource file }
  535.     DoCopyResource := ResError;         { return result code }
  536. END;
  537.  
  538. {$S Main}
  539. (******************************************************************************
  540. *
  541. * Public: DeviceLoopSim
  542. *
  543. * DeviceLoopSim was written by Forrest Tanaka for _develop_ #10, and simulates
  544. * the DeviceLoop trap for systems that don't have it implemented.  This
  545. * routine requires color QuickDraw.
  546. *
  547. * Forrest's routine calls a DeviceLoop drawing routine by function pointer,
  548. * but it can do that because it's written in C.  Pascal can't, so we provide
  549. * a nested routine to Handle calling a routine by pointer.  
  550. * CallDeviceLoopDrawingRoutine takes the same parameters, plus a function
  551. * pointer as the last one, and is simply 68000 inline assembly code to Move
  552. * the pointer into register A0 and jump through it.  Note that this would
  553. * require changing for PowerPC machines to build a native Pascal application.
  554. *
  555. ******************************************************************************)
  556.  
  557. PROCEDURE DeviceLoopSim(drawingRgn: RgnHandle; drawingProc: Ptr;
  558.                         userData: LONGINT; flags: LONGINT);
  559.  
  560.         { local procedure to handle calling by pointer }
  561.  
  562.         PROCEDURE CallDeviceLoopDrawingRoutine(depth, deviceFlags: INTEGER;
  563.                                                targetDevice: GDHandle;
  564.                                                userData: LONGINT;
  565.                                                theRoutine: Ptr);
  566.             INLINE $205F, $4ED0;             { MOVEA.L (A7)+,A0; JMP (A0) }
  567.  
  568.     VAR
  569.         aGDevice: GDHandle;            { Handle to the GDevice to draw in }
  570.         screenRgn,                    { Region for each screen }
  571.         savedClip: RgnHandle;        { saved clipRgn for the GrafPort }
  572.         screenRect: Rect;            { rectangle for each screen's bounds }
  573.  
  574.     BEGIN
  575.         savedClip := NewRgn;        { save the clip region of this port }
  576.         GetClip(savedClip);
  577.  
  578.         screenRgn := NewRgn;
  579.         aGDevice := GetDeviceList;
  580.         WHILE aGDevice <> NIL DO
  581.             BEGIN
  582.                 screenRect := aGDevice^^.gdRect;    { this screen's Rect }
  583.                 GlobalToLocal(screenRect.topLeft);    { make it local }
  584.                 GlobalToLocal(screenRect.botRight);
  585.                 RectRgn(screenRgn, screenRect);        { make a region of it }
  586.                 SectRgn(screenRgn, drawingRgn, screenRgn);
  587.                                     { and intersect it with our drawing rgn }
  588.  
  589.                 IF NOT EmptyRgn(screenRgn) THEN
  590.                     BEGIN
  591.                         SetClip(screenRgn);
  592.                         CallDeviceLoopDrawingRoutine(aGDevice^^.gdPMap^^.
  593.                                                      pixelSize,
  594.                                                      aGDevice^^.gdFlags,
  595.                                                      aGDevice, userData,
  596.                                                      drawingProc);
  597.                     END;
  598.                 aGDevice := GetNextDevice(aGDevice);
  599.             END;
  600.         SetClip(savedClip);            { restore the clip region }
  601.         DisposeRgn(savedClip);        { and dispose of the two regions we made }
  602.         DisposeRgn(screenRgn);
  603.     END; { DeviceLoopSim }
  604.  
  605. {$S Print}
  606. (******************************************************************************
  607. *
  608. * Public: NewPrJobMerge
  609. *
  610. * This routine is from Macintosh Technical Note "Fun with PrJobMerge" and
  611. * works around a problem where LaserWriter 7.x can accidentally mess up your
  612. * source print record as well as the destination one in PrJobMerge.  It
  613. * allocates a new print record that's a copy of the source one and uses
  614. * it as the source to PrJobMerge.
  615. *
  616. ******************************************************************************)
  617.  
  618.     PROCEDURE NewPrJobMerge(hPrintSrc, hPrintDst: THPrint);
  619.  
  620.     VAR
  621.         copyError: OSErr;            { error in creating new print record }    
  622.         hPrintTemp: THPrint;        { the temporary new print record }
  623.  
  624.     BEGIN
  625.         hPrintTemp := hPrintSrc;    { copy the handle with this statement }
  626.         copyError := HandToHand(Handle(hPrintTemp));
  627.                                     { this makes a new handle with the same
  628.                                       contents as the original }
  629.         PrSetError(copyError);         { save this error for later}
  630.         IF copyError = noErr THEN
  631.             BEGIN
  632.                 { hPrintTemp is now a copy of the original source record }
  633.                 PrJobMerge(hPrintTemp, hPrintDst); { This may mess up 
  634.                                                      hPrintTemp, but we don't 
  635.                                                      care }
  636.             END; {if copyError = noErr}
  637.         IF hPrintTemp <> NIL THEN
  638.             DisposeHandle(Handle(hPrintTemp));         { only a copy, remember!}
  639.     END; { NewPrJobMerge }
  640.  
  641. {$S Main}
  642. (******************************************************************************
  643. *
  644. * Public: GetNextWindow
  645. *
  646. * This function returns the window right after the one you pass in the
  647. * window list.  It's named after an Apple IIgs toolbox function I always
  648. * found incredibly handy which behaves identically.  The value is retrieved
  649. * from the window record in question.
  650. *
  651. ******************************************************************************)
  652.  
  653.     FUNCTION GetNextWindow(theWindow: WindowPtr): WindowPtr;
  654.  
  655.     BEGIN
  656.         GetNextWindow := WindowPtr(WindowPeek(theWindow)^.nextWindow);
  657.     END; { GetNextWindow }
  658.